use crate::location::Loation;
use crate::upstream::{get_upstream, new_internal_error};
use async_trait::async_trait;
use log::{error, info};
use pingora::prelude::HttpPeer;
use pingora::proxy::{http_proxy_service, HttpProxy, ProxyHttp, Session};
use pingora::server::configuration::ServerConf;
use pingora::services::listening::Service;
use std::sync::Arc;

pub struct Server {
    // 监听地址
    addr: String,
    locations: Vec<Loation>,
}
impl Server {
    pub fn new(addr: &str, locations: Vec<Loation>) -> Self {
        Self {
            addr: addr.to_string(),
            locations,
        }
    }
    // 监听对应地址，并返回对应的service
    pub fn run(self, conf: &Arc<ServerConf>) -> Service<HttpProxy<Server>> {
        let addr = self.addr.clone();
        // 创建转发http的服务
        let mut lb = http_proxy_service(conf, self);
        // 添加tcp监听地址
        lb.add_tcp(&addr);
        // 返回pingora service，用于添加至实例中
        lb
    }
}

/// Http转发的trait需要实现两个方法，`new_ctx`与`upstream_peer`
#[async_trait]
impl ProxyHttp for Server {
    type CTX = ();
    // 用于记录处理流程中的数据，暂时未使用
    fn new_ctx(&self) -> Self::CTX {}
    async fn upstream_peer(
        &self,
        session: &mut Session,
        _ctx: &mut (),
    ) -> pingora::Result<Box<HttpPeer>> {
        let header = session.req_header_mut();
        let path = header.uri.path().to_string();

        let location = self
            .locations
            .iter()
            .find(|item| item.matched(&path))
            .ok_or(new_internal_error(
                500,
                "无法获取匹配的location".to_string(),
            ))?;
        if let Some(uri) = location.rewrite(header) {
            println!("{}", uri);
            if let Err(e) = uri.parse::<http::Uri>().map(|uri| header.set_uri(uri)) {
                error!("error: {}", e.to_string());
            }
        }

        // 获取对应的upstream
        let up = get_upstream(&location.upstream).ok_or(new_internal_error(
            500,
            "无法获取对应的upstream".to_string(),
        ))?;
        // 根据upstream的load balancer获取对应的节点
        let backend = up
            .lb()
            .select(b"", 256)
            .ok_or(new_internal_error(500, "无法获取对应的backend".to_string()))?;
        info!("path: {path}, upstream peer is: {:?}", backend);

        let mut peer = Box::new(HttpPeer::new(backend.addr, up.tls, up.sni.clone()));
        peer.options = up.options.clone();

        Ok(peer)
    }
}
